﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using System.Threading;
using ForumLibrary;
using ForumLibrary.Data;

namespace ForumServer
{
    public class DForumManager : IForumManager, IObservable
    {
        private DataManager _db;
        hashEncode _encode;
        private ILog _logger;
        private List<ForumUser> _users;
        private const char _delimiter = '~';
        private const string _moderatorPassword = "Cuda";

        public DForumManager()
        {
            _db = new DataManager();
            _encode = new hashEncode();
            Logger.Logger log = new Logger.Logger("server", "Server");
            _logger = log.getLogger();
            _users = new List<ForumUser>();
            _db.loadUsers(ref _users);
        }

        public override string register(string firstName, string lastName, string username, string password, string email, string phone, bool isModerator)
        {
            string[] parameters = new string[]{firstName, lastName, username, password};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (!isModerator)
                {
                    if (_db.userExists(username))
                    {
                        _logger.Debug("Username " + username + " is not available");
                        return "Username is not available";
                    }
                }
                else
                {
                    if (_db.moderatorExists(username))
                    {
                        _logger.Debug("Username " + username + " is not available");
                        return "Username is not available";
                    }
                }
            }
            else
            {
                return checkResult;
            }

            string maskedPassword = _encode.HashEncode(password);
            _db.addUser(username, maskedPassword, email, phone, firstName, lastName, isModerator);
            _logger.Info("A user registered with the username: " + username);
            return "Registration Complete";
        }

        public override string login(string username, string password)
        {
            string[] parameters = new string[]{username, password};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_encode.HashEncode(password).Equals(_db.getUserPassword(username)))
                    {
                        if (!isLoggedIn(username))
                        {
                            changeUserLoginState(username, true);
                            _logger.Info("The user " + username + " logged in");
                            return "Login Complete: Welcome " + username;
                        }
                        else
                        {
                            return username + " is already logged in";
                        }
                    }
                    else
                    {
                        _logger.Debug("The user " + username + " tried to login with the wrong password");
                        return "Password incorrect";
                    }
                }
                else
                {
                    return "Username doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string moderatorLogin(string username, string password)
        {
            string[] parameters = new string[]{username, password};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.moderatorExists(username))
                {
                    if (_encode.HashEncode(password).Equals(_db.getUserPassword(username)))
                    {
                        changeUserLoginState(username, true);
                        _logger.Info("The moderator " + username + " logged in");
                        return "Login Complete: Welcome " + username;
                    }
                    else
                    {
                        _logger.Debug("The moderator " + username + " tried to login with the wrong password");
                        return "Password incorrect";
                    }
                }
                else
                {
                    return "Username doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string logout(string username)
        {
            string[] parameters = new string[]{username};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    changeUserLoginState(username, false);
                    _logger.Info("The user " + username + " logged out");
                    return "Logout successfully";
                }
                else
                {
                    return "Username doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string addFriend(string friendAdding, string friendAdded)
        {
            string[] parameters = new string[]{friendAdding, friendAdded};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(friendAdding) && _db.userExists(friendAdded))
                {
                    _db.addFriend(friendAdding, friendAdded);
                    _logger.Info("The user " + friendAdding + " Added " + friendAdded + " as a friend");
                    return "Friend added successfully";
                }
                else
                {
                    _logger.Debug("The user " + friendAdding + " failed to add " + friendAdded + " as a friend.");
                    return "Friend doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }
        
        public override string removeFriend(string friendRemoving, string friendRemoved)
        {
            string[] parameters = new string[]{friendRemoving, friendRemoved};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(friendRemoving) && _db.userExists(friendRemoved))
                {
                    _db.removeFriend(friendRemoving, friendRemoved);
                    _logger.Info("The user " + friendRemoving + " removed " + friendRemoved + " from friends list");
                    return "Friend removed successfully";
                }
                else
                {
                    _logger.Debug("The user " + friendRemoving + " failed to remove " + friendRemoved + " from friends list.");
                    return "Friend doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }
        
        public override string addThread(string username, string forumName, string header, string body)
        {
            string[] parameters = new string[]{username, forumName, header, body};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        _db.addThread(username, forumName, header, body);
                        string message = username + " Added the thread " + header + " to the Forum " + forumName;
                        notifyAllUsers(_users, Message.ThreadAdded, username, forumName, header);
                        notifyFriends(_users, Message.Notification, message, username, forumName, header);
                        _logger.Info("The user " + username + " added the thread " + "\"" + header + "\"" + " to the forum " + forumName);
                        return "Thread added successfully";
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string editThread(string username, string forumName, string threadName, string header, string body)
        {
            string[] parameters = new string[]{username, forumName, threadName, header, body};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.getThreadOwner(forumName, threadName).Equals(username))
                            {
                                _db.editThread(username, forumName, threadName, header, body);
                                notifyAllUsers(_users, Message.ThreadEdited, "", username, forumName, threadName);
                                string message = username + " Edited the thread " + threadName + " in the Forum " + forumName;
                                notifyFriends(_users, Message.Notification, message, username, forumName, threadName);
                                _logger.Info("The user " + username + " edited the thread " + "\"" + header + "\"" + " in the forum " + forumName);
                                return "Thread edited successfully";
                            }
                            else
                            {
                                _logger.Debug("The user " + username + " failed to edit the thread " + "\"" + header + "\"" + " in the forum " + forumName);
                                return "Cannot edit a thread that you didn't create";
                            }
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string deleteThread(string username, string forumName, string threadName)
        {
            string[] parameters = new string[]{username, forumName, threadName};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.moderatorExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            _db.deleteThread(username, forumName, threadName);
                            _logger.Info("The user " + username + " deleted the thread " + "\"" + threadName + "\"" + " from the forum " + forumName);
                            return "Thread removed successfully";
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User isn't moderator and can't remove threads";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string addPost(string username, string forumName, string threadName, string header, string body)
        {
            string[] parameters = new string[]{username, forumName, threadName, header, body};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            _db.addPost(username, forumName, threadName, header, body);
                            notifyAllUsers(_users, Message.PostAdded, "", username, forumName, threadName);
                            notifyThreadCreator(_users, Message.Notification, username, forumName, threadName);
                            string message = username + " Added a post in the thread " + threadName + " in the forum " + forumName;
                            notifyFriends(_users, Message.Notification, message, username, forumName, threadName);
                            _logger.Info("The user " + username + " added the post " + "\"" + header + "\"" +
                                    " in the thread " + threadName + " in the forum " + forumName);
                            return "Post Added successfully";
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string editPost(string username, string forumName, string threadName, string postName, string newHeader, string newBody)
        {
            string[] parameters = new string[]{username, forumName, threadName, postName, newHeader, newBody};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.postExists(forumName, threadName, postName))
                            {

                                if (_db.getPostOwner(forumName, threadName, postName).Equals(username))
                                {
                                    _db.editPost(username, forumName, threadName, postName, newHeader, newBody);
                                    notifyAllUsers(_users, Message.PostEdited, "",username, forumName, threadName);
                                    _logger.Info("The user " + username + " edited the post " + "\"" + postName + "\"" +
                                        " in the thread " + threadName + " in the forum " + forumName);
                                    return "Post edited successfully";
                                }
                                else
                                {
                                    return "Cannot edit a post that you didn't create";
                                }
                            }
                            else
                            {
                                return "Post doesn't exist";
                            }
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }

        public override string deletePost(string username, string forumName, string threadName, string postName)
        {
            string[] parameters = new string[]{username, forumName, threadName, postName};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    if (_db.forumExists(forumName))
                    {
                        if (_db.threadExists(forumName, threadName))
                        {
                            if (_db.postExists(forumName, threadName, postName))
                            {

                                if (_db.getPostOwner(forumName, threadName, postName).Equals(username))
                                {
                                    _db.deletePost(username, forumName, threadName, postName);
                                    notifyAllUsers(_users, Message.PostDeleted, "", username, forumName, threadName);
                                    _logger.Info("The user " + username + " deleted the post " + "\"" + postName + "\"" +
                                        " in the thread " + threadName + " in the forum " + forumName);
                                    return "Post removed successfully";
                                }
                                else
                                {
                                    return "Cannot delete a post that you didn't create";
                                }
                            }
                            else
                            {
                                return "Post doesn't exist";
                            }
                        }
                        else
                        {
                            return "Thread doesn't exist";
                        }
                    }
                    else
                    {
                        return "Forum doesn't exist";
                    }
                }
                else
                {
                    return "User doesn't exist";
                }
            }
            else
            {
                return checkResult;
            }
        }
        
        public override string searchForNewFriend(string searchedUser)
        {
            string[] parameters = new string[] { searchedUser};
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                return _db.searchForNewFriend(searchedUser);
            }
            else
            {
                return checkResult;
            }
        }
        
        public override string getUsersList()
        {
            string usernames = "";
            List<string> users = _db.getUsersList();
            foreach (string username in users)
            {
                usernames += username + "\n";
            }
            return usernames;
        }

        public override string getUserDetails(string username)
        {
            string[] parameters = new string[] { username };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if (_db.userExists(username))
                {
                    ForumUser client = _users.Find(user => user.Username.ToLower().Equals(username.ToLower()));
                    string details = "";
                    if (client != null)
                    {
                        details = _db.getUserDetails(username);
                        details += client.LoginState.ToString() + "\n";
                    }
                    return details;
                }
            }
            else
            {
                return checkResult;
            }
            return "User doesn't exist";
        }

        public override string getFriendslist(string username)
        {
            string[] parameters = new string[] { username };
            string checkResult = checkValidParameters(parameters);
            if (checkResult.Equals(""))
            {
                if(_db.userExists(username))
                    return _db.getFriendslist(username);
            }
            else
            {
                return checkResult;
            }
            return "User doesn't exist";
        }
        
        public override string getForumsTopics()
        {
            string forumsTopics = "";
            List<ForumLibrary.Data.Forum> forums = getForums();
            foreach (ForumLibrary.Data.Forum forum in forums)
            {
                forumsTopics += forum.Topic + "\n";
            }
            return forumsTopics;
        }

        public List<ForumLibrary.Data.Forum> getForums()
        {
            return _db.getForums();
        }

        public override string getThreadsHeaders(string forumName)
        {
            string[] parameters = new string[] { forumName };
            string checkResult = checkValidParameters(parameters);
            string threadsHeaders = "";
            if (checkResult.Equals(""))
            {
                List<string> headers = _db.getThreadsHeaders(forumName);
                foreach (string header in headers)
                {
                    threadsHeaders += header + "~";
                }
            }
            else
            {
                return checkResult;
            }
            return threadsHeaders;
        }

        public override string getThreadDetails(string forumName, string threadName)
        {
            string[] parameters = new string[] { forumName, threadName };
            string checkResult = checkValidParameters(parameters);
            string threadDetails = "";
            if (checkResult.Equals(""))
            {
                if (_db.forumExists(forumName))
                {
                    if (_db.threadExists(forumName, threadName))
                    {
                        return _db.getThreadDetails(forumName, threadName);
                    }
                }
            }
            else
            {
                return checkResult;
            }
            return threadDetails;
        }

        public override string getThreadOwner(string forumName, string threadName)
        {
            string[] parameters = new string[] { forumName, threadName };
            string checkResult = checkValidParameters(parameters);
            string threadDetails = "";
            if (checkResult.Equals(""))
            {
                if (_db.forumExists(forumName))
                {
                    if (_db.threadExists(forumName, threadName))
                    {
                        return _db.getThreadOwner(forumName, threadName);
                    }
                }
            }
            else
            {
                return checkResult;
            }
            return threadDetails;
        }

        public override string getPostHeaders(string forumName, string threadName)
        {
            string[] parameters = new string[] { forumName, threadName };
            string checkResult = checkValidParameters(parameters);
            string postHeaders = "";
            if (checkResult.Equals(""))
            {
                if (_db.forumExists(forumName))
                {
                    if (_db.threadExists(forumName, threadName))
                    {
                        List<string> posts = _db.getPostHeaders(forumName, threadName);
                        foreach (string p in posts)
                        {
                            postHeaders += p + '~';
                        }
                    }
                }
            }
            else
            {
                return checkResult;
            }
            return postHeaders;
        }

        public override string getPostDetails(string forumName, string threadName, string postName)
        {
            string[] parameters = new string[] { forumName, threadName, postName };
            string checkResult = checkValidParameters(parameters);
            string postDetails = "";
            if (checkResult.Equals(""))
            {
                if (_db.postExists(forumName, threadName, postName))
                {
                    postDetails = _db.getPostDetails(forumName, threadName, postName);
                }
            }
            else
            {
                return checkResult;
            }
            return postDetails;
        }

        public override string getPostsDetails(string forumName, string threadName)
        {
            string[] parameters = new string[] { forumName, threadName };
            string checkResult = checkValidParameters(parameters);
            string postDetails = "";
            if (checkResult.Equals(""))
            {
                List<Post> posts = getPosts(forumName, threadName);
                foreach (Post post in posts)
                {
                    postDetails += post.Header + '_' + post.Body + _delimiter;
                }
            }
            else
            {
                return checkResult;
            }
            return postDetails;
        }

        public List<Post> getPosts(string forumName, string threadName)
        {
            List<Post> posts = new List<Post>();
            if (_db.forumExists(forumName))
            {
                if (_db.threadExists(forumName, threadName))
                {
                    return _db.getPosts(forumName, threadName);
                }
            }
            return posts;
        }

        public override bool isPostOwnerOrModerator(String forumName, String threadName, String postName, String username)
        {
            if (_db.moderatorExists(username))
                return true;
            if (_db.postExists(forumName, threadName, postName))
            {
                return _db.getPostOwner(forumName, threadName, postName).Equals(username);
            }
            return false;
        }

        public override bool checkModeratorPassword(string pass)
        {
            return pass.Equals(_moderatorPassword);
        }

        private void changeUserLoginState(string username, bool state)
        {
            ForumUser client = _users.Find(user => user.Username.Equals(username));
            if (client != null)
            {
                client.LoginState = state;
            }
        }

        private bool isLoggedIn(string username)
        {
            ForumUser client = _users.Find(user => user.Username.Equals(username));
            if (client != null)
            {
                return client.LoginState;
            }
            return false;
        }

        public override string deletePost(string username, string forumName, string threadName, string postName, string body)
        {
            return "stub";
        }
        
        public override void demo()
        {
            //------------------------Create the forums localy on the server------------------------//
            _db.addForum("Movies");
            _db.addForum("Music");
            //------------------------Add some users for testing------------------------//
            _db.addUser("Adiel", "password", "Ashrov@mail.com", "123", "Adiel", "Ashrov", false);
            _db.addUser("Etai", "password", "Hazan@mail.com", "123", "Etai", "Hazan", false);
            _db.addUser("Benny", "1", "Mihaeli@mail.com", "123", "Benny", "Mihaeli", true);
            _db.addUser("Chezi", "password", "Halpert@mail.com", "123", "Chezi", "Halpert", false);
            //------------------------Add some threads for testing------------------------//
            _db.addThread("etai", "Movies", "Inception", "Best movie of 2010!");
            _db.addThread("chezi", "Movies", "The Dark Knight", "Best movie of 2008!");
            _db.addThread("etai", "Music", "Adele", "Adele's 19 and 21 are great albums!");
            _db.addThread("etai", "Movies", "Irish", "Irish music is owseme!");
        }

        public override void subscribe(IObserver observer, string username, bool loginState)
        {
            ForumUser client = _users.Find(u => u.Username.ToLower().Equals(username.ToLower()));
            ForumUser user = new ForumUser(username, observer, loginState);
            if (client == null)
                _users.Add(user);
            else if (user.Controller == null)
            {
                user.Controller = observer;
                user.LoginState = loginState;
            }
        }

        public override void unsubscribe(string username)
        {
            ForumUser client = _users.Find(user => user.Username.Equals(username));
            if(client != null)
                _users.Remove(client);
        }

        public void notifyFriends(List<ForumUser> users, Message messageType, string message, string username = "", string forumName = "", string threadName = "")
        {
            List<IObserver> controllers = new List<IObserver>();
            foreach(ForumUser user in users)
            {
                if (_db.isFriend(user.Username, username) && !user.Username.Equals(username))
                    controllers.Add(user.Controller);
            }
            new System.Threading.Thread(run).Start(new Notify(controllers, messageType, message, username, forumName, threadName));
        }

        public void notifyThreadCreator(List<ForumUser> users, Message messageType, string username = "", string forumName = "", string threadName = "")
        {
            List<IObserver> controllers = new List<IObserver>();
            string notification = username + " Added a post in your thread " + threadName + " in the forum " + forumName;
            string threadCreator = _db.getThreadOwner(forumName, threadName);
            foreach (ForumUser user in users)
            {
                if (user.Username.Equals(threadCreator) && !threadCreator.Equals(username))
                {
                    controllers.Add(user.Controller);
                    break;
                }
            }
            new System.Threading.Thread(run).Start(new Notify(controllers, messageType, notification, username, forumName, threadName));
        }

        public void notifyAllUsers(List<ForumUser> users, Message messageType, string message, string username = "", string forumName = "", string threadName = "")
        {
            List<IObserver> controllers = new List<IObserver>();
            foreach (ForumUser user in users)
            {
                controllers.Add(user.Controller);
            }
            new System.Threading.Thread(run).Start(new Notify(controllers, messageType, message, username, forumName, threadName));
        }
        
        public void run(Object obj)
        {
            Notify n = (Notify)obj;
            foreach (IObserver user in n._controllers)
                user.update(n._message, n._notification, n._username, n._forumName, n._threadName);

        }

        private string checkValidParameters(string[] parameters)
        {
            string checkResult = "";
            foreach (string param in parameters)
                if (param.Contains(_delimiter))
                {
                    checkResult = "Fields cannot contain the special character ~";
                    break;
                }
            return checkResult;
        }

        class Notify
        {
            public List<IObserver> _controllers;
            public Message _message;
            public string _notification;
            public string _username;
            public string _forumName;
            public string _threadName;

            public Notify(List<IObserver> controllers, Message message, string notification, string username = "", string forumName = "", string threadName = "")
            {
                _controllers = controllers;
                _notification = notification;
                _message = message;
                _username = username;
                _forumName = forumName;
                _threadName = threadName;
            }
        }

    }
}
